/**
 * 微博抢红包
 * 两个步骤
 * 1 抢红包，设置红包总金额，红包个数，返回抢红包的地址
 * curl "http://localhost:8080/set?uid=1&money=100&num=100"
 * 2 抢红包，先到先得，随机得到红包金额
 * curl "http://localhost:8080/get?id=1&uid=1"
 * 注意：
 * 线程不安全1，红包列表 packageList map 的并发读写会产生异常
 * 测试方法： wrk -t10 -c10 -d5  "http://localhost:8080/set?uid=1&money=100&num=10"
 * fatal error: concurrent map writes
 * 线程不安全2，红包里面的金额切片 packageList map[uint32][]uint 并发读写不安全，虽然不会报错
 */
package main

import (
	"fmt"
	"github.com/kataras/iris"
	"github.com/kataras/iris/mvc"
	"math/rand"
	"sync"
	"time"
)

// 红包列表
var (

	//packList = make(map[uint32][]uint)
	packList = new(sync.Map)
)

type lotteryController struct {
	Ctx iris.Context
}

func newApp() (app *iris.Application) {
	app = iris.New()
	mvc.New(app.Party("/")).Handle(&lotteryController{})
	return app
}

func main() {
	var (
		app *iris.Application
	)

	app = newApp()
	_ = app.Run(iris.Addr(":8080"))
}

func (c *lotteryController) Get() map[uint32][2]int {

	rs := make(map[uint32][2]int)

	//for id, list := range packList {
	//	var money int
	//	for _, v := range list {
	//		money += int(v)
	//	}
	//	rs[id] = [2]int{len(list), money}
	//}

	packList.Range(func(key, value interface{}) bool {
		id := key.(uint32)
		list := value.([]uint)
		var money int
		for _, v := range list {
			money += int(v)
		}
		rs[id] = [2]int{len(list), money}

		return true
	})
	fmt.Println(packList)

	return rs
}

func (c *lotteryController) GetSet() string {
	uid, errUid := c.Ctx.URLParamInt("uid")
	money, errMoney := c.Ctx.URLParamFloat64("money")
	num, errNum := c.Ctx.URLParamInt("num")

	if errUid != nil || errMoney != nil || errNum != nil {
		return fmt.Sprintf("参数格式异常，errUid=%d, errMoney=%d, errNum=%d\n", errUid, errMoney, errNum)
	}

	moneyTotal := int(money * 100)

	if uid < 1 || moneyTotal < num || num < 1 {
		return fmt.Sprintf("参数数值异常, uid=%d, money=%d, num=%d\n", uid, money, num)
	}

	// 金额分配算法
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	rMax := 0.55 // 随机分配最大值
	list := make([]uint, num)
	leftMoney := moneyTotal
	leftNum := num
	// 开始分配金额到每一个红包
	for leftNum > 0 {
		if leftNum == 1 {
			// 最后一个红包，剩余的全部金额都给它
			list[num-1] = uint(leftMoney)
			break
		}

		if leftMoney == leftNum {
			for i := num - leftNum; i < num; i++ {
				list[i] = 1
			}
			break
		}

		rMoney := int(float64(leftMoney-leftNum) * rMax)
		m := r.Intn(rMoney)
		if m < 1 {
			m = 1
		}
		list[num-leftNum] = uint(m)
		leftMoney -= m
		leftNum--
	}

	// 红包的唯一ID
	id := r.Uint32()
	//packList[id] = list
	packList.Store(id, list)
	// 返回红包的地址
	return fmt.Sprintf("/get?id=%d&uid=%d&num=%d", id, uid, num)
}

func (c *lotteryController) GetGet() string {
	uid, errUid := c.Ctx.URLParamInt("uid")
	id, errId := c.Ctx.URLParamInt("id")

	if errUid != nil || errId != nil {
		return fmt.Sprintf("")
	}

	if uid < 1 || id < 1 {
		return fmt.Sprintf("")
	}

	//list, ok := packList[uint32(id)]
	list1, ok := packList.Load(uint32(id))
	list := list1.([]int)
	if !ok || len(list) < 1 {
		return fmt.Sprintf("红包不存在,id=%d\n", id)
	}

	// 分配一个随机数
	r := rand.New(rand.NewSource(time.Now().UnixNano()))

	i := r.Intn(len(list))
	money := list[i]
	// 更新一下红包列表中的信息
	if len(list) > 1 {
		if i == len(list)-1 {
			//packList[uint32(id)] = list[:i]
			packList.Store(uint32(id), list[:i])
		} else if i == 0 {
			//packList[uint32(id)] = list[1:]
			packList.Store(uint32(id), list[1:])
		} else {
			//packList[uint32(id)] = append(list[:i], list[i+1:]...)
			packList.Store(uint32(id), append(list[:i], list[i+1:]...))

		}
	} else {
		//delete(packList, uint32(id))
		packList.Delete(uint32(id))
	}

	return fmt.Sprintf("恭喜你抢到一个红包，金额为：%d\n", money)
}
